﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using C5;
using System.Threading;
using LimitOrderBookSim.Commons;

namespace LimitOrderBookSim.Assets
{
    public class OrderBook
    {
         TreeSet<Limit> Bid { get; set; }
         TreeSet<Limit> Ask { get; set; }

         Queue<int> Queue { get; set; }
         OrderPool Pool { get; set; }

         Writer bid_log;
         Writer ask_log;
         Writer trade_log;

        

         public String BestBidAsk()
         {
             String result = string.Empty;

             lock (Bid)
             {
                 lock (Ask)
                 {
                     result = String.Format("{0}:{1}",
                         (Bid.Count>0)?Bid.FindMin().Price: -1, (Ask.Count>0)?Ask.FindMax().Price:-1);
                 }
             }

             return result;
         }

         public String High()
         {
             String result = string.Empty;

             lock (Ask)
             {
                 result = (Ask.Count > 0) ? Bid.FindMax().Price.ToString() : "-1"; //Ask.FindMax().Price.ToString();
             }

             return result;
         }

         public String AvgBidAsk()
         {
             String result = string.Empty;

             lock (Bid)
             {
                 lock (Ask)
                 {
                     result = String.Format("{0}:{1}",
                         (Bid.Count > 0) ? Math.Round(Bid.Sum(v => (v.Price * v.Volume)) / Bid.Sum(v => v.Size), 2) : -1,
                         (Ask.Count > 0) ? Math.Round(Ask.Sum(v => (v.Price * v.Volume)) / Ask.Sum(v => v.Size), 2) : -1);
                 }
             }

             return result;
         }

         public String Low()
         {
             String result = string.Empty;

             lock (Bid)
             {
                 result = (Bid.Count > 0) ? Ask.FindMin().Price.ToString() : "-1"; 
             }

             return result;
         }

        public int Volume { get; set; }

        public override string ToString()
        {
            String result = string.Empty;

            result += "****ASK****" + Environment.NewLine;

            lock (Ask)
            {
                foreach (Limit limit in Ask)
                {
                    result += "       " + limit.ToString();
                }

            }

            result += Environment.NewLine + Environment.NewLine + "****BID****" + Environment.NewLine;

            lock (Bid)
            {
                foreach (Limit limit in Bid)
                {
                    result += "       " + limit.ToString();
                }

            }

            return result;
        }

        public OrderBook(OrderPool pool)
        {
            Bid = new TreeSet<Limit>();
            Ask = new TreeSet<Limit>();

            Queue = new Queue<int>();

            Pool = pool;

            trade_log = new Writer("trades.txt");
            bid_log = new Writer("bid.txt");
            ask_log = new Writer("ask.txt");

            ThreadPool.QueueUserWorkItem(HandleQueue, null);
        }

        public  void Dump()
        {
            Console.WriteLine(Environment.NewLine + "****ASK****" + Environment.NewLine);

            var result = Ask.OrderBy(o => (o.Price));

            foreach (Limit o in result)
            {
                Console.WriteLine(o.ToString());
            }

            Console.WriteLine(Environment.NewLine + "****BID****" + Environment.NewLine);

            result = Bid.OrderBy(o => (o.Price));

            foreach (Limit o in result)
            {
                Console.WriteLine(o.ToString());
            }
        }

        public  void Process(Message message)
        {

            var target = (message.Order.Type == TYPE.BID) ? Ask : Bid;

            Limit limit = null;

            //if (message.Order.Type == TYPE.BID)
            //{
                Monitor.Enter(target);
                if (message.Event == EVENT.SUBMISSION)
                {


                    if (target.Find(b => (b.Price == message.Order.Price), out limit))
                    {
                        limit.Orders.Add(message.Order.ID);
                        limit.Size += message.Order.Size;
                        limit.Volume += message.Order.Size;
                    }
                    else
                    {
                        limit = new Limit()
                        {
                            Price = message.Order.Price,
                            Size = message.Order.Size,
                            Volume = message.Order.Size
                        };


                        limit.Orders.Add(message.Order.ID);
                        target.Add(limit);
                    }

                    if (limit.Size == 0)
                    {
                        target.Remove(limit);
                    }

                    Volume += message.Order.Size;

                     #if LOG
                        bid_log.Write(message.Order.ID.ToString() + Environment.NewLine);
                     #endif

                }
                else if (message.Event == EVENT.CANCELLATION)
                {
                    if (target.Find(b => (b.Price == message.Order.Price), out limit))
                    {
                        if (limit.Orders.Remove(message.Order.ID))
                        {
                            limit.Volume += message.Order.Size;
                        }

                        Volume -= message.Order.Size;
                        limit.Volume -= message.Order.Size;

                        if (limit.Size == 0)
                        {
                            target.Remove(limit);
                        }
                    }

                }

                Monitor.Exit(target);
           // }

            #if LOG
            ask_log.Write(message.Order.ID.ToString() + Environment.NewLine);
            #endif

            Monitor.Enter(Queue);
            Queue.Enqueue(message.Order.ID);
            Monitor.Exit(Queue);
        }

         void HandleQueue(Object obj)
        {
            while (true)
            {
                int message;

                if (Queue.Count > 0)
                {
                    String log = string.Empty;
                    message = Queue.Dequeue();
                    Order order = Pool.Get(message);

                    var target = (order.Type == TYPE.BID) ? Ask : Bid;
                    var result = target.Where(o => (o.Price <= order.Price)).OrderBy(o => (o.Price));

                    lock (target)
                    {
                        foreach (Limit o in result)
                        {
                            lock (o.Orders)
                            {
                                for (int i = 0; i < o.Orders.Count; i++)
                                {
                                    if (order.Size == 0)
                                    {
                                        break;
                                    }

                                    int or = o.Orders[i];

                                    Order currentOrder = Pool.Get(or);

                                    if (currentOrder.Size > 0 && (!currentOrder.Equals(order)))
                                    {
                                        if (order.Size > currentOrder.Size)
                                        {
                                            order.Size -= currentOrder.Size;
                                            log += String.Format("{0}:{1}:{2}\n", DateTime.Now.Ticks, currentOrder.Price, currentOrder.Size);
                                            currentOrder.Size = 0;
                                        }
                                        else
                                        {
                                            currentOrder.Size -= order.Size;
                                            log += String.Format("{0}:{1}:{2}\n", DateTime.Now.Ticks, currentOrder.Price, order.Size);
                                            order.Size = 0;
                                        }

                                        trade_log.Write(log);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
